<?php


namespace app\tool;

class Chatgpt
{
	
	const FORM_URLENCODED = "application/x-www-form-urlencoded";
	
	/**
	 * Generate a sorted query parameter string from parameter array.
	 * such as array('b' => 1, 'a' => 2) to "a=1&b=2"
	 *
	 * @param array $paramArr
	 * @return string sorted query string
	 */
	function getSortedParameterStr(array $paramArr): string
	{
		ksort($paramArr);
		$tmpArr = array();
		foreach ($paramArr as $k => $v) {
			$tmp = $k;
			if (!empty($v)) {
				$tmp .= ("=" . $v);
			}
			$tmpArr[] = $tmp;
		}
		
		return join('&', $tmpArr);
	}
	
	/**
	 * Send a HTTP request with App Authorization
	 *
	 * @param $apiAppKey string API App Key
	 * @param $apiAppSecret string API App Secret
	 * @param $method string HTTP Method of API
	 * @param $url string Request URL of API, note that environment path (/release) is not allowed
	 * @param $contentType string Request Content-Type header, set empty if request body is not needed
	 * @param $acceptHeader string Accept HTTP request header
	 * @param $reqBody string|null Request Body, set null if request body is not needed
	 * @param $formParam array|null form parameters array, set null if not form request
	 * @param $algorithm string|null Encryption algorithm: sha1, sha256, sha384, sha512, SM3, default to sha1
	 * @param $customHeaders array|null Custom HTTP Headers, such as `array('x-header-a' => 1)`
	 */
	function sendRequestWithAppAuth(string $apiAppKey, string $apiAppSecret, string $method, string $url, string $contentType, string $acceptHeader, string $reqBody = null, array $formParam = null, string $algorithm = null, array $customHeaders = null)
	{
		$contentMD5 = "";
		$isForm = ($contentType == self::FORM_URLENCODED);
		// Note: ContentMd5 is empty for application/x-www-form-urlencoded request
		if ($isForm) {
			assert(!is_null($formParam), "formParam is required for form request");
			// generate request body from form parameters
			$reqBody = $this->getSortedParameterStr($formParam);
		} elseif (!is_null($reqBody)) {
			// get content md5 for signing the request later
			$contentMD5 = base64_encode(md5($reqBody));
		}
		
		if (null === $algorithm) {
			$algorithm = "sha1";
		}
		
		// ===================================
		// STEP 1: Generate the string to sign
		// ===================================
		
		dump("1. URL:$url");
		
		// Note:
		// 1. parameters needs to be sorted in alphabetical order
		// 2. parameters include both query parameters and form parameters
		$paramArr = array();
		$parsedUrl = parse_url($url);
		if (!empty($parsedUrl['query'])) {
			parse_str($parsedUrl['query'], $paramArr);
		}
		if (!empty($formParam)) {
			$paramArr = array_merge($paramArr, $formParam);
		}
		
		$pathAndParam = $parsedUrl['path'];
		if (!empty($paramArr)) {
			$pathAndParam = $pathAndParam . '?' . $this->getSortedParameterStr($paramArr);
		}
		
		$xDateHeader = gmstrftime('%a, %d %b %Y %T %Z', time());
		$strToSign = sprintf("x-date: %s\n%s\n%s\n%s\n%s\n%s",
			$xDateHeader, $method, $acceptHeader, $contentType, $contentMD5, $pathAndParam);
		
		// Print stringToSign for debugging if authorization failed with 401
		$strToSignDebug = str_replace("\n", "#", $strToSign);
		dump("2. StringToSign:$strToSignDebug");
		
		// ===============================================================================
		// STEP 2: generate the signature (Authorization header) based on the stringToSign
		// ===============================================================================
		
		// Encode the string with HMAC and base64.
		$sign = base64_encode(hash_hmac($algorithm, $strToSign, $apiAppSecret, TRUE));
		$authHeader = sprintf(
			'hmac id="%s", algorithm="hmac-%s", headers="x-date", signature="%s"',
			$apiAppKey, $algorithm, $sign
		);
		
		$headers = array(
			'Host:' . $parsedUrl['host'],
			'Accept:' . $acceptHeader,
			'X-Date:' . $xDateHeader,
			'Authorization:' . $authHeader,
		);
		if (!empty($contentType)) {
			$headers[] = "Content-Type:" . $contentType;
		}
		if (!empty($contentMD5)) {
			$headers[] = "Content-MD5:" . $contentMD5;
		}
		if (is_array($customHeaders)) {
			foreach ($customHeaders as $k => $v) {
				$headers[] = $k . ":" . $v;
			}
		}
		
		dump("3. Request Headers:");
		dump($headers);
		
		// ============================
		// STEP 3: send the API request
		// ============================
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_TIMEOUT, 60);
		if (!empty($reqBody)) {
			curl_setopt($ch, CURLOPT_POSTFIELDS, $reqBody); // only required if request body is present
		}
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
		$data = curl_exec($ch);
		dd($data);
		if (curl_errno($ch)) {
			print "Error: " . curl_error($ch);
		} else {
			echo "Response: \n";
			var_dump($data);
			curl_close($ch);
		}
	}
}